import socket
import json
from re import findall
import json
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from threading import Thread
from time import time
from time import sleep

class Main:
	def __init__(self):
		'''
		参数说明:
		self.go_channel 格式:{("111.111.111.111",8888):{"channel":[]}}
		self.webtalk    就是可以发送给服务器消息
		alive 和 listen 不用管他是什么
		self.ui         提示用户输入指令的无限循环
		'''
		self.go_channel = {}

		self.webtalk = WebTalk()

		alive = Thread(target=self.alive)
		alive.start()

		listen = Thread(target=self.listen)
		listen.start()

		self.ui()

	def ui(self):
		print(r'''██╗  ██╗   ██████╗   ███╗   ███╗  ███████╗  ██████╗
██║  ██║  ██╔═══██╗  ████╗ ████║  ██╔════╝  ██╔══██╗
███████║  ██║   ██║  ██╔████╔██║  █████╗    ██████╔╝
██╔══██║  ██║   ██║  ██║╚██╔╝██║  ██╔══╝    ██╔══██╗
██║  ██║  ╚██████╔╝  ██║ ╚═╝ ██║  ███████╗  ██║  ██║
╚═╝  ╚═╝   ╚═════╝   ╚═╝     ╚═╝  ╚══════╝  ╚═╝  ╚═╝

WELCOME TO HOMER!
SAY WHAT YOU WANT TO SAY HERE!

TYPE "go home"''')
		while True:
			msg = input(f"$ ")
			if msg != "":
				msg_split = msg.strip().split()
				self.handle(msg_split)
				print("")

	def handle(self,msg_split):
		'''
		专门处理用户输入的命令，像"go","help"这样的
		msg_split 是 一个列表
		如 : ["go","182.0.0.6","8","home"]
		'''
		# f_long 是列表的长度
		# 也就是 (命令+所有参数) 的长度
		f_long = len(msg_split)

		if msg_split[0] == "help":
			'''
			帮助文档
			'''
			print("# join the channel : \"go ip port channel\" or \"go channel\"\n# config           : config in config/config.json")
		
		elif msg_split[0] == "go":
			'''
			加入聊天室的命令 go ip port channel
			需要的参数： 哪个ip,哪个端口(port),哪个房间(channel)
			'''
			
			# 判断ip,port是否正确
			if f_long == 2:
				with open("config/config.json") as fp:
					config_all = json.loads(fp.read())
					ip = config_all["ip"]
					port = config_all["port"]
					channel = msg_split[1]

					go = Go(channel=channel,webtalk=self.webtalk,ip=ip,port=port)
					if (ip,port) in self.go_channel:
						self.go_channel[(ip,port)]["channel"].append(go)
					else:
						self.go_channel[(ip,port)] = {"channel":[]}
						self.go_channel[(ip,port)]["channel"].append(go)
					go.start()
					print(f"joined {ip}-{port} {channel} successfully!!!")
					self.webtalk.go(channel=channel,ip=ip,port=port)

			if f_long == 4:
				ip_pattern = r"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
				port_pattern = r"[0-9]{1,5}"

				try:
					if findall(ip_pattern,msg_split[1]) and findall(port_pattern,msg_split[2]):
						if int(msg_split[2]) <= 65535 and int(msg_split[2]) >= 1:
							# 以上为“判断ip,port是否正确部分”

							ip = msg_split[1]
							port = int(msg_split[2])
							channel = msg_split[3]

							# 先是向服务器发送加入的消息
							# 然后创建一个新的Go实例添加进self.go_channel
							go = Go(channel=channel,webtalk=self.webtalk,ip=ip,port=port)
							if (ip,port) in self.go_channel:
								self.go_channel[(ip,port)]["channel"].append(go)
							else:
								self.go_channel[(ip,port)] = {"channel":[]}
								self.go_channel[(ip,port)]["channel"].append(go)
							go.start()
							print(f"joined {ip}-{port} {channel} successfully!!!")
							self.webtalk.go(channel=channel,ip=ip,port=port)
				except:
					print('Unknown Error!')
		
		else:
			print("请输入正确的语法")

	def listen(self):
		'''
		监听来自服务器的消息
		'''
		while True:
			# 如果加入了房间的话就进循环
			# 如果此时用户没有加入任何一个房间的话就没必要一直循环浪费资源了是吧

			# 表示加入的所有房间的状态，如果True在里面就说明有房间是打开状态
			channel_states = []

			for detail in self.go_channel.values():
				for go_obj in detail["channel"]:
					channel_states.append(go_obj.running)

			while True in channel_states:
				# ...
				channel_states = []
				for detail in self.go_channel.values():
					for go_obj in detail["channel"]:
						channel_states.append(go_obj.running)
				try:
					data = json.loads(self.webtalk.s.recv(1024).decode())
					# 收到消息的处理
					if data["cmd"] == "msg":
						address = (data["address"][0],data["address"][1])
						if address in self.go_channel:
							for go_obj in self.go_channel[address]["channel"]:
								if go_obj.channel == data["channel"] and go_obj.running:
									# 这个循环避免tkinter窗口还未加载好就text.insert而导致的无效
									while True:
										try:
											go_obj.all_text.insert(END,data["content"]["text"])
											break
										except:
											pass
				except:
					pass

	def alive(self):
		'''
		alive

		不停地发送{"cmd":"alive"}给服务器
		告诉各个服务器我还在线
		如果服务器超过了一定的时间没有收到客户端的alive消息
		则将此客户端视为“断线”
		每三十秒发一次
		'''

		# loop: 
		while True:
			for ip_port,detail in self.go_channel.items():
				if True in [obj.running for obj in detail["channel"]]:
					self.webtalk.alive(ip_port[0],ip_port[1])
			sleep(10)


class Go(Thread):
	def __init__(self,channel,webtalk,ip,port):
		'''
		参数的说明：
		 self.channel 是这个加入的房间的名字
		 self.webtalk 是用来发送消息给服务器的
		 self.ip      是这个加入的房间所属的服务器的ip
		 self.port    是这个加入的房间所属的服务器的port
		 self.running 是表示这个房间没有被关闭
		'''
		Thread.__init__(self)
		self.channel = channel
		self.webtalk = webtalk
		self.ip = ip
		self.port = port
		self.running = True

	def on_closing(self):
		'''
		如果这个用户点击了gui窗口的关闭按钮（就右上角的那个
		就发送给服务器，告诉他"我退出了"
		同时把此Go实例的running改成False
		然后将窗口关闭
		'''
		self.webtalk.left(channel=self.channel,ip=self.ip,port=self.port)
		self.running = False
		self.windows.destroy()

	def chat(self,event):
		'''
		发送信息
		'''
		# 获取输入框的值
		msg = self.enter_text.get()
		# 如果消息不是"空"就发送
		if msg != "":
			self.enter_text.delete(0,END)
			self.webtalk.chat(content=msg,channel=self.channel,ip=self.ip,port=self.port)
		return "break"

	def run(self):
		'''
		tkinter创建的gun窗口
		emm....没有什么可说的
		我就调了几天天~
		'''
		self.windows = Tk()
		self.windows.title(f"# Channel : {self.channel}")

		self.all_text = ScrolledText(
			self.windows,
			height=30,
			width=80)
		self.all_text.pack()

		self.enter_text = Entry(
			self.windows,
			width=80)
		self.enter_text.pack()

		self.windows.protocol("WM_DELETE_WINDOW", self.on_closing)
		self.enter_text.bind("<Return>",self.chat)
		self.windows.mainloop()


class WebTalk:
	'''
	数据包的构造
	'''
	def __init__(self):
		self.s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

	def go(self,channel,ip,port):
		'''
		加入房间
		channel:房间名称
		告诉服务器加入了哪个房间
		'''
		data = json.dumps({"cmd":"go","channel":channel})
		self.s.sendto(data.encode(),(ip,port))

	def chat(self,content,channel,ip,port):
		'''
		发送信息
		content:内容
		channel:房间名称
		'''
		data = json.dumps({"cmd":"chat","content":content,"channel":channel})
		self.s.sendto(data.encode(),(ip,port))

	def left(self,channel,ip,port):
		'''
		离开
		channel:房间名称
		'''
		data = json.dumps({"cmd":"left","channel":channel})
		self.s.sendto(data.encode(),(ip,port))

	def alive(self,ip,port):
		data = json.dumps({"cmd":"alive"})
		self.s.sendto(data.encode(),(ip,port))


if __name__ == '__main__':
	main = Main()